﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CashFlow.Common;
using CashFlow.Entities;
using CashFlow.Repository;
using System.Diagnostics.Contracts;
using CashFlow.BusinessContracts;

using CashFlow.AzureRepository;namespace CashFlow.BusinessComponents
{
	public class CashFlowComponent : ICashFlowComponent
	{
		private string tenantId;
		private Tenant tenant;
		private IEnumerable<Bill> bills;
		private DateRange dateRange;
		private List<DateTime> months = new List<DateTime>();

		private ITenantRepository tenantRepository;
		private IBillRepository billRepository;

		private readonly DateTimeUtil dateUtil = new DateTimeUtil();

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the <see cref="T:CashFlowComponent"/> class.
		/// </summary>
		public CashFlowComponent() : this(AppState.TenantId)
		{
		}

		public CashFlowComponent(string tenantId) : 
			this (tenantId, new AzureTenantRepository(), new AzureBillRepository())
		{
		}

		public CashFlowComponent(
			string tenantId,
			ITenantRepository tenantRepository,
			IBillRepository billRepository)
		{
			Contract.Requires(!string.IsNullOrEmpty(tenantId), "Tenant id cannot be null.");
			Contract.Requires(tenantRepository != null, "Tenant Repository cannot be null.");
			Contract.Requires(billRepository != null, "Bill Repository cannot be null.");

			this.tenantId = tenantId;
			this.tenantRepository = tenantRepository;
			this.billRepository = billRepository;
		}
		#endregion

		public void GetCashFlow()
		{
			GetCashFlow(new DateRange(tenant.InitialBalanceDate, DateTime.UtcNow));
		}

		public void GetCashFlow(DateRange dates)
		{
			GetTenant();
			GetBills();
			this.dateRange = dates;
			months = dateUtil.GetMonths(dateRange.MinDate, dateRange.MaxDate).ToList();

			var monthlyData = GetCashFlowEvents();
		}

		public void Calculate()
		{
			//DEBUG
			FakeSetup();
			var periodBills = this.bills.
				Where(b => b.FirstDate <= this.dateRange.MaxDate).
				Select(b => b)
			.ToList();

			var billInstances = periodBills.Select(b => new 
			{ 
				Bill = b, 
				Dates = b.Recurrence.GetDates(this.dateRange) 
			})
			.ToList();

			var cashFlowEvents = billInstances.SelectMany(b => 
				b.Dates.Select(d => 
					new CashFlowEvent
					{
						Bill = b.Bill, 
						Date = d,
						Order = 1,
						Balance = 0d
					}))
			.ToList();

			var initialBalanceEvent = Enumerable.Range(0, 1).Select(x => 
				new CashFlowEvent
					{
						Bill = new Bill {PayeeName = "Initial Balance", Amount = tenant.InitialBalanceAmount}, 
						Date = tenant.InitialBalanceDate,
						Order = 0,
						Balance = tenant.InitialBalanceAmount
					})
			.ToList();

			var futureEventWithInitialBalance = initialBalanceEvent.
				Union(cashFlowEvents.
					Where(x => x.Date >= tenant.InitialBalanceDate)).
				OrderBy(x => x.Order).ThenBy(x => x.Date)
			.ToList();

			var futureCashFlow = cashFlowEvents.OrderBy(x => x.Date).
				Aggregate((total, next) => {
					total.Balance += next.Bill.Amount;
					next.Balance = total.Balance;
					return next;
				});

			var balance = tenant.InitialBalanceAmount;
			var final = cashFlowEvents.OrderBy(x => x.Date).Select(x => new CashFlowEvent
			{
				Bill = x.Bill,
				Date = x.Date,
				Balance = balance += x.Bill.Amount
			}).ToList();

			var result = futureEventWithInitialBalance.ToList();
		}

		private void FakeSetup()
		{
			dateRange = new DateRange(new DateTime(2011, 4, 1), new DateTime(2011, 4, 30));
			tenant = new Tenant { InitialBalanceDate = new DateTime(2011, 4, 10), InitialBalanceAmount = 100 };
			bills = new List<Bill> 
			{ 
				new Bill{Recurrence = new RecurrenceOnce {FirstDate = new DateTime(2011, 4, 1)}, Amount = 10},
				new Bill{Recurrence = new RecurrenceOnce{FirstDate = new DateTime(2011, 4, 11)}, Amount = 20},
				new Bill{Recurrence = new RecurrenceOnce{FirstDate = new DateTime(2011, 4, 21)}, Amount = 30},
				new Bill{Recurrence = new RecurrenceOnce{FirstDate = new DateTime(2011, 4, 30)}, Amount = 40},
			};
		}

		private IEnumerable<MonthCashFlow> GetCashFlowEvents()
		{
			List<MonthCashFlow> cashFlow = null;
			var numberOfMonths = months.Count();
			if (numberOfMonths > 0)
			{
				cashFlow = months.
					OrderByDescending(d => d).
					Select(m => GetMonthCashFlow(m, bills)).
					ToList();
			}
			//TODO: vvs p3 prev statement - converting to List, but can I do IEnumerable?
			return cashFlow;
		}

		public MonthCashFlow GetMonthCashFlow(DateTime date)
		{
			return GetMonthCashFlow(date, bills);
		}

		public MonthCashFlow GetMonthCashFlow(DateTime date, IEnumerable<Bill> bills)
		{
			Contract.Requires(date.Day == 1, "date should be 1st day of the month");

			var cashFlow = new MonthCashFlow(date);
			var dateRange = new DateRange(date, dateUtil.MonthLastDate(date));

			var cashFlowData = bills.SelectMany(
				bill => bill.Recurrence.GetNextDates(dateRange).
				Select(cashFlowDate => new CashFlowEvent {
						Date = cashFlowDate,
						Bill = bill
				})).
				OrderByDescending(cf => cf.Date);

			cashFlow.Items = cashFlowData;

			return cashFlow;
		}

		public void CalcBalances(MonthCashFlow cashFlow, DateTime startBalanceDate, double startBalance)
		{
			var balance = startBalance;
			if (startBalanceDate.Day == 1)
			{
				foreach (var item in cashFlow.Items)
				{
					item.Balance = balance + item.Bill.Amount;
					balance = item.Balance;
				}
				cashFlow.BeginingBalance = startBalance;
				cashFlow.EndingBalance = balance;
			}
			else
			{
				foreach (var item in cashFlow.Items.Where(i => i.Date >= startBalanceDate))
				{
					item.Balance = balance + item.Bill.Amount;
					balance = item.Balance;
				}
				cashFlow.EndingBalance = balance;

				balance = startBalance;
				foreach (var item in cashFlow.Items.Where(i => i.Date < startBalanceDate).OrderByDescending(d => d.Date))
				{
					item.Balance = balance - item.Bill.Amount;
					balance = item.Balance;
				}
				cashFlow.BeginingBalance = balance;
			}
		}

		private void GetBills()
		{
			bills = billRepository.GetAll();
		}

		private void GetTenant()
		{
			Contract.Ensures(tenant != null, "Tenant doesn't exist.");
			tenant = tenantRepository.GetById();
		}


		public void Calc()
		{
			//var bills = GetBills();
			//var billComponent = new BillComponent();
			//if (!HasBills(bills))
			//  return;

			//var dateTimeUtil = new DateTimeUtil();
			//var today = DateTime.Now; //Should get user time zone
			//var todayYearMonth = dateTimeUtil.ToYYMM(today);

			//Tuple<DateTime, double> initialBalance = 
			//  GetInitialBalance() ??
			//  new Tuple<DateTime, double>(dateTimeUtil.MonthFirstDate(today), 0);

			//IEnumerable<CashFlowEvent> cashFlow = billComponent.GetCashFlow(today);

			//cashFlow.
			//  Where(e => e.Date >= initialBalance.Item1).
			//  Aggregate((e, total) => {
			//    e.Balance = total.Balance + e.Bill.Amount;
			//    return e;
			//  });




		}
	}
}
